/**
 * Copyright (c) 2013 Nokia Corporation. All rights reserved.
 * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation. 
 * Oracle and Java are trademarks or registered trademarks of Oracle and/or its
 * affiliates. Other product and company names mentioned herein may be trademarks
 * or trade names of their respective owners. 
 * See LICENSE.TXT for license information.
 */

package com.nokia.example.composedui;

import java.io.IOException;
import java.util.Stack;
import java.util.Vector;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Image;

import com.nokia.mid.ui.CategoryBar;
import com.nokia.mid.ui.ElementListener;
import com.nokia.mid.ui.IconCommand;

import com.nokia.example.composedui.model.ItemStorage;
import com.nokia.example.composedui.utils.AppDataStorage;
import com.nokia.example.composedui.utils.Confirmation;
import com.nokia.example.composedui.utils.PersistableState;

/**
 * Tab navigation and drawing handler. Tab navigation is handled through a
 * CategoryBar object. Implements ElementListener interface to be able to detect
 * selection events from the CategoryBar as well as ViewManager interface to be
 * able to handle view switching request from individual views.
 */
public class TabManager
    implements ElementListener,
               ViewManager
{
    // Constants
    private static final String STORAGE_ID_FOR_NUMBER_OF_TABS = "numberOfTabs";
    private static final String STORAGE_ID_FOR_DEFAULT_TAB_INDEX = "defaultTabIndex";
    private static final String STORAGE_ID_FOR_TAB_ICON_NAME = "tabIconName";
    private static final String STORAGE_ID_FOR_TAB_LABEL = "tabLabel";
    private static final String STORAGE_ID_FOR_NUMBER_OF_VIEWS_IN_STACK = "numberOfViews";
    private static final String STORAGE_ID_FOR_VIEW_CLASS_NAME = "classNameOfView";

    // Members
    protected final Main midlet;
    protected CategoryBar categoryBar;
    protected boolean redrawNeeded;
    protected Vector iconCommands = new Vector();
    protected Vector iconCommandIconFileNames = new Vector();
    protected Vector tabStacks = new Vector();

    /**
     * Class constructor. Just saves the application MIDlet as a member, so that
     * actual view changes can be directed to MIDlet's Display.
     * @param midlet The MIDlet instance.
     */
    public TabManager(Main midlet) {
        this.midlet = midlet;
        this.redrawNeeded = false;
    }

    /**
     * @return The Display instance associated with the MIDlet.
     */
    public Display getDisplay() {
        return Display.getDisplay(midlet);
    }


    // Tab handling

    /**
     * Adds a tab to the tab bar with a custom icon. View title is added as the
     * command label for the tab.
     * @param iconName Filename of the command icon relative to the "res" folder.
     * @param label Command label for the tab.
     */
    public void addTab(Displayable view, String iconName) {
        addTab(view, iconName, view.getTitle());
    }

    /**
     * Adds a tab to the tab bar with a custom icon and command label.
     * @param view Displayable view which is shown as the first view of the tab
     * @param iconName Filename of the command icon relative to the "res" folder.
     * @param label Command label for the tab.
     * @throws IllegalArgumentException If the given view is not persistable.
     */
    public void addTab(Displayable view, String iconName, String label)
        throws IllegalArgumentException
    {
        if (!(view instanceof PersistableState)) {
            throw new IllegalArgumentException("Displayable has to implement PersistableState");
        }
        
        Image icon = null;
        
        try {
            icon = Image.createImage(iconName);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        
        iconCommands.addElement(new IconCommand(label, icon, null, Command.SCREEN, 1));
        iconCommandIconFileNames.addElement(iconName);
        Stack tabStack = new Stack();
        tabStack.push(view);
        tabStacks.addElement(tabStack);
        redrawNeeded = true;
    }

    /**
     * Shows tab of the given index.
     * @param index Index of the tab to be shown.
     */
    public void showTab(int index) {
        drawTabBar();
        
        if (index >= 0 && index < tabStacks.size()) {
            categoryBar.setSelectedIndex(index);
            AppDataStorage.getInstance(midlet).put(STORAGE_ID_FOR_DEFAULT_TAB_INDEX,
                                                   Integer.toString(index));
            Stack tabStack = (Stack) (tabStacks.elementAt(index));
            Displayable view = (Displayable) (tabStack.peek());
            Display.getDisplay(midlet).setCurrent(view);
            toggleTabBarVisibilityBasedOn(tabStack);
        }
        else {
            // Invalid index -> do nothing
        }
    }

    /**
     * Shows tab of the default index.
     */
    public void showTab() {
        showTab(getDefaultTabIndex());
    }

    /**
     * Draws the actual tab bar to the screen after all tabs are added to the
     * view. There is no reason to call this method multiple times, because it
     * basically just initializes a new CategoryBar instance, and most probably
     * it doesn't change during the MIDlet life cycle.
     * @return Instance of the created category bar.
     */
    protected CategoryBar drawTabBar() {
        if (redrawNeeded) {
            // Create an array out of Vector iconCommands contents
            IconCommand[] iconCommandsArray = new IconCommand[iconCommands.size()];
            
            for (int i = 0; i < iconCommands.size(); i++) {
                iconCommandsArray[i] = (IconCommand) iconCommands.elementAt(i);
            }
            
            // Initialize a new CategoryBar instance and overwrite the possible
            // old one
            categoryBar = new CategoryBar(iconCommandsArray, true);
            
            // Set it visible and change the selected tab color based on the
            // MIDlet Display
            categoryBar.setVisibility(true);
            categoryBar.setElementListener(this);
            Display display = Display.getDisplay(midlet);
            categoryBar.setHighlightColour(
                display.getColor(Display.COLOR_HIGHLIGHTED_BACKGROUND));
            redrawNeeded = false;
        }
        
        return categoryBar;
    }

    /**
     * Handles all CategoryBar selection events.
     * @param categoryBar CategoryBar from which the event was fired (in this
     *        case it will always be the local this.categoryBar instance)
     * @param selectedIndex Index of the selected tab
     * @see com.nokia.mid.ui.ElementListener#notifyElementSelected(CategoryBar, int)
     */
    public void notifyElementSelected(CategoryBar categoryBar, int selectedIndex) {
        if (selectedIndex == ElementListener.BACK) {
            goBack();
        }
        else {
            showTab(selectedIndex);
        }
    }


    // View stack handling by implementing ViewManager interface

    /**
     * @see ViewManager#showView(javax.microedition.lcdui.Displayable)
     */
    public void showView(Displayable view) {
        if (view instanceof PersistableState) {
            int currentTabIndex = categoryBar.getSelectedIndex();
            Stack tabStack = (Stack) (tabStacks.elementAt(currentTabIndex));
            tabStack.push(view);
            Display.getDisplay(midlet).setCurrent(view);
            toggleTabBarVisibilityBasedOn(tabStack);
        }
        else {
            throw new IllegalArgumentException("Displayable has to implement PersistableState");
        }
    }

    /**
     * @see ViewManager#goBack()
     */
    public void goBack() {
        int currentTabIndex = categoryBar.getSelectedIndex();
        Stack tabStack = (Stack) (tabStacks.elementAt(currentTabIndex));
        
        if (tabStack.size() == 1) {
            midlet.closeApp(false);
        }
        else {
            tabStack.pop();
            Displayable view = (Displayable) (tabStack.peek());
            Display.getDisplay(midlet).setCurrent(view);
            toggleTabBarVisibilityBasedOn(tabStack);
        }
    }

    /**
     * Toggle tab bar visibility based on the number of items in a single tab
     * stack. Tab bar is not shown on sub views of a tab.
     * @param currentTabStack
     */
    private void toggleTabBarVisibilityBasedOn(Stack currentTabStack) {
        // Prevent accidental tab bar flickering by checking the current
        // visibility first
        boolean visibilityShouldBe = (currentTabStack.size() <= 1);
        
        if (categoryBar.getVisibility() != visibilityShouldBe) {
            categoryBar.setVisibility(visibilityShouldBe);
        }
    }
    


    // Save tab stack states to AppDataStorage
    
    private int getDefaultTabIndex() {
        String savedIndex = AppDataStorage.getInstance(midlet).valueByKey(
            STORAGE_ID_FOR_DEFAULT_TAB_INDEX);
        
        if (savedIndex != null) {
            return Integer.parseInt(savedIndex);
        }
        
        return 0;
    }

    public void saveViewsToAppDataStorage() {
        AppDataStorage appDataStorage = AppDataStorage.getInstance(midlet);
        appDataStorage.put(STORAGE_ID_FOR_NUMBER_OF_TABS, Integer.toString(tabStacks.size()));
        
        for (int t = 0; t < tabStacks.size(); t++) {
            Vector tabStack = (Vector)tabStacks.elementAt(t);
            
            String tabIconFileName = (String)iconCommandIconFileNames.elementAt(t);
            String tabLabel = ((IconCommand)iconCommands.elementAt(t)).getLabel();
            appDataStorage.put(STORAGE_ID_FOR_TAB_ICON_NAME + t, tabIconFileName);
            appDataStorage.put(STORAGE_ID_FOR_TAB_LABEL + t, tabLabel);
            
            appDataStorage.put(STORAGE_ID_FOR_NUMBER_OF_VIEWS_IN_STACK + t,
                               Integer.toString(tabStack.size()));
            
            for (int v = 0; v < tabStack.size(); v++) {
                Displayable view = (Displayable)tabStack.elementAt(v);
                String viewName = view.getClass().getName();
                String viewId = viewName + t + v;
                appDataStorage.put(STORAGE_ID_FOR_VIEW_CLASS_NAME + t + v, viewName);
                
                if (view instanceof PersistableState) {
                    ((PersistableState)view).saveInstance(appDataStorage, viewId);
                }
            }
            
        }
    }

    /**
     * Tries to restore the application state (views).
     * @param itemStorage The item storage instance.
     * @return True if successful, false otherwise.
     */
    public boolean restoreViewsFromAppDataStorage(ItemStorage itemStorage) {
        AppDataStorage appDataStorage = AppDataStorage.getInstance(midlet);
        
        try {
            final int numberOfTabs =
                Integer.parseInt(appDataStorage.valueByKey(STORAGE_ID_FOR_NUMBER_OF_TABS));
            
            System.out.println("TabManager::restoreViewsFromAppDataStorage(): "
                + "Number of tabs is " + numberOfTabs);
            
            for (int t = 0; t < numberOfTabs; t++) {
                String tabIconFileName =
                    appDataStorage.valueByKey(STORAGE_ID_FOR_TAB_ICON_NAME + t);
                String tabLabel = appDataStorage.valueByKey(STORAGE_ID_FOR_TAB_LABEL + t);
                
                Displayable previousView = new Form(""); // Dummy Displayable
                
                int numberOfViews = Integer.parseInt(appDataStorage.valueByKey(
                    STORAGE_ID_FOR_NUMBER_OF_VIEWS_IN_STACK + t));
                
                for (int v = 0; v < numberOfViews; v++) {
                    String viewName = appDataStorage.valueByKey(
                        STORAGE_ID_FOR_VIEW_CLASS_NAME + t + v);
                    Displayable view = (Displayable)Class.forName(viewName).newInstance();
                    String viewId = viewName + t + v;
                    
                    if (view instanceof PersistableState) {
                        ((PersistableState)view).restoreInstance(itemStorage.getActiveDataItem(),
                                                                 itemStorage,
                                                                 this,
                                                                 appDataStorage,
                                                                 viewId);
                        
                        if (view instanceof Confirmation) {
                            ((Confirmation)view).restoreCommandListener((CommandListener)previousView);
                        }
                    }
                    
                    if (v == 0) {
                        addTab(view, tabIconFileName, tabLabel);
                    }
                    else {
                        ((Vector)tabStacks.elementAt(t)).addElement(view);
                    }
                    
                    previousView = view;
                }
            }
            
            showTab();
        }
        catch (Exception e) {
            System.out.println("TabManager::restoreViewsFromAppDataStorage(): "
                + "Restoring views failed due to " + e + ".");
            tabStacks.removeAllElements();
            iconCommands.removeAllElements();
            iconCommandIconFileNames.removeAllElements();
            return false;
        }
        
        System.out.println("Restoring tabstacks from RMS succeeded");
        return true;
    }
}
